package wiki.xsx.core.dynamic.annotation;

import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

/**
 * 动态注解自动配置
 * @author xsx
 * @date 2020/6/16
 * @since 1.8
 * <p>
 * Copyright (c) 2020 xsx All Rights Reserved.
 * dynamic-annotation-spring-boot-starter is licensed under the Mulan PSL v1.
 * You can use this software according to the terms and conditions of the Mulan PSL v1.
 * You may obtain a copy of Mulan PSL v1 at:
 * http://license.coscl.org.cn/MulanPSL
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v1 for more details.
 * </p>
 */
@Configuration
@ConditionalOnClass(Environment.class)
@ConditionalOnBean(annotation = EnableDynamicAnnotation.class)
public class DynamicAnnotationAutoConfig {

    /**
     * 上下文
     */
    @Resource(type = ApplicationContext.class)
    private ApplicationContext applicationContext;

    /**
     * 自动注解后置处理器
     * @param environment 环境
     * @return 返回后置处理器
     */
    @Bean
    public BeanPostProcessor dynamicAnnotationPostProcessor(Environment environment) {
        return new DynamicAnnotationPostProcessor(environment, this.getDynamicAnnotation());
    }

    /**
     * 获取动态注解标记
     * @return 返回动态注解标记
     */
    private EnableDynamicAnnotation getDynamicAnnotation() {
        EnableDynamicAnnotation dynamicAnnotation = null;
        try {
            Annotation[] annotations = this.applicationContext.getBeansWithAnnotation(EnableDynamicAnnotation.class).values().iterator().next().getClass().getSuperclass().getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation instanceof EnableDynamicAnnotation) {
                    dynamicAnnotation = ((EnableDynamicAnnotation) annotation);
                    break;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return dynamicAnnotation;
    }

    /**
     * 动态注解后置处理器实现
     */
    static class DynamicAnnotationPostProcessor implements BeanPostProcessor, Ordered {

        /**
         * 注解字段
         */
        private static final String FIELD = "memberValues";
        /**
         * 环境
         */
        private final Environment environment;
        /**
         * 动态注解标记
         */
        private final EnableDynamicAnnotation dynamicAnnotation;

        /**
         * 有参构造
         * @param environment 环境
         * @param dynamicAnnotation 动态注解标记
         */
        public DynamicAnnotationPostProcessor(Environment environment, EnableDynamicAnnotation dynamicAnnotation) {
            this.environment = environment;
            this.dynamicAnnotation = dynamicAnnotation;
        }

        /**
         * 前置处理
         * @param bean bean对象
         * @param beanName bean名称
         * @return 返回对象
         * @throws BeansException bean异常
         */
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            try {
                Class<?> objClz;
                if (AopUtils.isAopProxy(bean)) {
                    objClz = AopUtils.getTargetClass(bean);
                } else {
                    objClz = bean.getClass();
                }
                if (this.dynamicAnnotation!=null) {
                    if (!this.containsExclude(objClz)) {
                        this.annotationReplace(objClz);
                    }
                }
            } catch (Exception e) {
                throw new BeanCreationException(beanName, e);
            }
            return bean;
        }

        /**
         * 排序
         * @return 返回排序号
         */
        @Override
        public int getOrder() {
            return -1;
        }

        /**
         * 注解替换
         * @param objClz bean对象
         * @throws NoSuchFieldException 无此字段异常
         * @throws IllegalAccessException 非法访问异常
         */
        private void annotationReplace(Class<?> objClz) throws NoSuchFieldException, IllegalAccessException {
            Field[] fields;
            Method[] methods;
            Class<? extends Annotation>[] annotationTypes = this.dynamicAnnotation.value();
            for (Class<? extends Annotation> annotationType : annotationTypes) {
                if (objClz.isAnnotationPresent(annotationType)) {
                    this.replace(objClz.getAnnotation(annotationType));
                }
                fields = objClz.getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(annotationType)) {
                        this.replace(field.getAnnotation(annotationType));
                    }
                }
                methods = objClz.getDeclaredMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(annotationType)) {
                        this.replace(method.getAnnotation(annotationType));
                    }
                }
            }
        }

        /**
         * 替换
         * @param replaceAnnotation 替换注解
         * @throws NoSuchFieldException 无此字段异常
         * @throws IllegalAccessException 非法访问异常
         */
        @SuppressWarnings("unchecked")
        private void replace(Annotation replaceAnnotation) throws NoSuchFieldException, IllegalAccessException {
            InvocationHandler handler = Proxy.getInvocationHandler(replaceAnnotation);
            Field field = handler.getClass().getDeclaredField(FIELD);
            field.setAccessible(true);
            ((Map<Object, Object>) field.get(handler)).replaceAll((key, value) -> this.environment.resolveRequiredPlaceholders(value.toString()));
        }

        /**
         * 是否包含排除类
         * @param objClz 待验证类
         * @return 返回布尔值，true为是，false为否
         */
        private boolean containsExclude(Class<?> objClz) {
            boolean flag = false;
            Class<?>[] classes = this.dynamicAnnotation.exclude();
            for (Class<?> clz : classes) {
                if (objClz.isAssignableFrom(clz)) {
                    flag = true;
                    break;
                }
            }
            return flag;
        }
    }
}
